Skip to content

fix(security): workspace sandbox avoid time-of-check/time-of-use (TOCTOU) races#464

Merged
xiaket merged 19 commits intosipeed:mainfrom
0x5487:fix/workspace-path-traversal
Feb 23, 2026
Merged

fix(security): workspace sandbox avoid time-of-check/time-of-use (TOCTOU) races#464
xiaket merged 19 commits intosipeed:mainfrom
0x5487:fix/workspace-path-traversal

Conversation

@0x5487
Copy link
Contributor

@0x5487 0x5487 commented Feb 19, 2026

📝 Description

Fixes path traversal vulnerability in filesystem tools.

Background:
PicoClaw agents are designed to operate within a strictly restricted "workspace" directory to prevent unauthorized access to the host filesystem. However, the previous implementation relied on manual path string validation, which was susceptible to bypasses using relative paths (e.g., ../) or symlinks and it may still be vulnerable to time-of-check/time-of-use (TOCTOU) races, where the attacker creates a symlink after the program’s check

Changes:
This PR introduces os.Root (new in Go 1.24) to enforce these boundaries at the operating system interaction level. By opening the workspace root once and performing all subsequent file operations relative to that root handle, we eliminate the possibility of escaping the workspace, even with malicious crafted paths.

https://go.dev/blog/osroot

🗣️ Type of Change

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 📖 Documentation update
  • ⚡ Code refactoring (no functional changes, no api changes)

🤖 AI Code Generation

  • 🤖 Fully AI-generated (100% AI, 0% Human)
  • [] 🛠️ Mostly AI-generated (AI draft, Human verified/modified)
  • 👨‍💻 Mostly Human-written (Human lead, AI assisted or none)

🔗 Related Issue

#258

📚 Technical Context (Skip for Docs)

  • Reference URL: Go 1.24 Release Notes - os.Root
  • Reasoning:
    • Security Fix (Path Traversal): Replaced manual path validation with os.Root (new in Go 1.24), which provides safer, OS-level or logical enforcement of filesystem boundaries. This prevents attacks using ../ or malicious symlinks to escape the workspace.
    • Refactoring: Unified file access patterns using executeInRoot helper, simplifying security checks and ensuring consistency across all filesystem tools (EditFileTool, WriteFileTool, etc.).

🧪 Test Environment

  • Hardware:
  • OS:
  • Model/Provider:
  • Channels:

📸 Evidence (Optional)

Click to view Logs/Screenshots

☑️ Checklist

  • My code/docs follow the style of this project.
  • I have performed a self-review of my own changes.
  • [] I have updated the documentation accordingly.

@0x5487 0x5487 changed the title Fix/workspace path traversal fix(security): workspace path traversal Feb 19, 2026
Copy link

@nikolasdehor nikolasdehor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent security improvement — os.Root is the correct solution for workspace sandboxing, eliminating TOCTOU races and symlink escapes that string-based validation cannot fully prevent.

A few minor issues (non-blocking):

  1. Missing space in error message (ReadFileTool restricted branch):
    "failed to read file:file not found" → should be "failed to read file: file not found"

  2. WriteFileTool inconsistent result in executeInRoot branch: returns &ToolResult{Silent: true} with no message, while the unrestricted branch returns SilentResult(fmt.Sprintf("File written: %s", path)). The LLM loses the path confirmation.

  3. ListDirTool code duplication: The restricted branch duplicates formatting logic instead of reusing formatDirEntries().

None of these are security-critical — the core os.Root approach is sound. LGTM.

…y formatting logic, and enhance `WriteFileTool`'s result message.
@0x5487
Copy link
Contributor Author

0x5487 commented Feb 19, 2026

Thanks for review. I have fixed those issues.

…xisting files and add tests for directory creation functionality.
Copy link

@nikolasdehor nikolasdehor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong security improvement. Using os.Root (Go 1.24+) to enforce workspace boundaries at the OS level is the right approach — it eliminates the TOCTOU race conditions inherent in the old validatePath + EvalSymlinks approach.

A few observations:

  1. Double validation in getSafeRelPath — You're checking for ../ escapes manually before passing to os.Root, but os.Root itself already rejects paths that escape the root. This is defense-in-depth, which is fine, but worth a comment noting it's intentional belt-and-suspenders rather than the primary security boundary.

  2. EditFileTool read-then-write is not atomicroot.Open() for reading, then root.Create() for writing has a window where the file could change. The old code had the same issue, so this isn't a regression, but worth noting for a future follow-up (e.g., os.Root.OpenFile with O_RDWR and in-place rewrite).

  3. root.Create() truncates — In EditFileTool, if f.Write() fails partway through, you'll have a truncated file. Consider writing to a temp file within the root and renaming, or at minimum documenting this risk.

  4. Test coverage is good — The symlink escape test, empty workspace test, mkdirAllInRoot test, and nested directory creation test cover the important cases. The relaxed error message matching in the tests is pragmatic given platform differences.

  5. Code duplication — The !t.restrict branches duplicate the entire logic. Consider extracting the core logic (read/validate/replace/write) into a shared function that takes a reader/writer interface, reducing the surface for divergence bugs. Not a blocker though.

Overall this is a clear security win. The os.Root approach is simpler and more robust than manual path validation. LGTM.

…h host and sandboxed I/O, improving atomic writes and error handling.
@0x5487
Copy link
Contributor Author

0x5487 commented Feb 20, 2026

This is incredibly valuable feedback. I’ve redesigned the solution accordingly—please take a look when you have a moment. Thanks!

@0x5487 0x5487 changed the title fix(security): workspace path traversal fix(security): workspace sandbox avoid time-of-check/time-of-use (TOCTOU) races Feb 22, 2026
…face and updating tools to use it directly, removing `os.Root` dependency from `sandboxFs`.
@lxowalle
Copy link
Collaborator

Hi @0x5487 , Please fix the CI errors, thanks!

@0x5487
Copy link
Contributor Author

0x5487 commented Feb 22, 2026

@lxowalle
I run make fmt lint locally. Now, 0 issues. Please try it again, thanks

@lxowalle lxowalle mentioned this pull request Feb 22, 2026
5 tasks
@lxowalle
Copy link
Collaborator

The CI did not pass. I couldn't find the pkg/tools/registry_test.go file when I pulled your code. Please make sure you have merged the latest branch. Please run make check and make lint and try again.

@0x5487
Copy link
Contributor Author

0x5487 commented Feb 22, 2026

  1. merged with latest branch
  2. run make check lint
    please try again. thank you

@nikolasdehor
Copy link

This fixes the TOCTOU race in workspace sandbox, which is a subset of the broader security hardening in #331 (shell escape bypass, symlink TOCTOU, SSRF, working_dir validation). #482 also addresses the symlink TOCTOU specifically. Suggesting consolidation to #331 which covers the widest attack surface.

@xiaket xiaket merged commit 19c6983 into sipeed:main Feb 23, 2026
2 checks passed
@Orgmar
Copy link
Contributor

Orgmar commented Feb 25, 2026

@0x5487 Using Go 1.24's os.Root to enforce workspace boundaries at the OS level is a really clean approach. Eliminates the TOCTOU window entirely instead of just trying to mitigate it with string checks. Nice upgrade from manual path validation.

We're building a PicoClaw Dev Group on Discord for contributors to connect and collaborate. If you'd like to join, send an email to support@sipeed.com with the subject [Join PicoClaw Dev Group] 0x5487 and we'll send you the invite link.

This was referenced Feb 27, 2026
liangzhang-keepmoving pushed a commit to liangzhang-keepmoving/picoclaw that referenced this pull request Mar 2, 2026
…TOU) races (sipeed#464)

* chore: Update default host bindings from 0.0.0.0 to 127.0.0.1 for various services and examples.

* config: Update default host bindings to 0.0.0.0 for improved Docker accessibility and add related documentation.

* refactor: reimplement filesystem tools with `os.OpenRoot` for enhanced security and simplified path validation.

* chore: revert other PR content from this branch

* docs: Update Chinese README.

* docs: Update Chinese README.

* docs: Update Chinese README.

* refactor: Reorder filesystem helper functions, extract directory entry formatting logic, and enhance `WriteFileTool`'s result message.

* feat: Enhance `mkdirAllInRoot` to prevent creating directories over existing files and add tests for directory creation functionality.

* Refactor filesystem tools to use a `fileReadWriter` interface for both host and sandboxed I/O, improving atomic writes and error handling.

* refactor: unify filesystem read/write operations with atomic write guarantees and clearer naming.

* refactor: rename `appendFileWithRW` function to `appendFile`

* refactor: unify filesystem access by introducing a `fileSystem` interface and updating tools to use it directly, removing `os.Root` dependency from `sandboxFs`.

* chore: run make fmt

* fix: `validatePath` now returns an error when the workspace is empty.
hyperwd pushed a commit to hyperwd/picoclaw that referenced this pull request Mar 5, 2026
…TOU) races (sipeed#464)

* chore: Update default host bindings from 0.0.0.0 to 127.0.0.1 for various services and examples.

* config: Update default host bindings to 0.0.0.0 for improved Docker accessibility and add related documentation.

* refactor: reimplement filesystem tools with `os.OpenRoot` for enhanced security and simplified path validation.

* chore: revert other PR content from this branch

* docs: Update Chinese README.

* docs: Update Chinese README.

* docs: Update Chinese README.

* refactor: Reorder filesystem helper functions, extract directory entry formatting logic, and enhance `WriteFileTool`'s result message.

* feat: Enhance `mkdirAllInRoot` to prevent creating directories over existing files and add tests for directory creation functionality.

* Refactor filesystem tools to use a `fileReadWriter` interface for both host and sandboxed I/O, improving atomic writes and error handling.

* refactor: unify filesystem read/write operations with atomic write guarantees and clearer naming.

* refactor: rename `appendFileWithRW` function to `appendFile`

* refactor: unify filesystem access by introducing a `fileSystem` interface and updating tools to use it directly, removing `os.Root` dependency from `sandboxFs`.

* chore: run make fmt

* fix: `validatePath` now returns an error when the workspace is empty.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants